#region LicenseHeader

// Copyright 2012 The Trustees of Indiana University.  All rights reserved.
// 
// The Indiana University licenses this file to you under the Apache License, 
// Version 2.0 (the "License"); you may not use this file except in compliance 
// with the License.  You may obtain a copy of the License at 
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
// See the License for the specific language governing permissions and 
// limitations under the License.
// 
// @author Thilina Gunarathne (tgunarat@indiana.edu)

#endregion

#region

using System;
using System.Collections.Generic;
using System.IO;

#endregion

namespace NAligner
{
    public class FastaParser
    {
        public const int _EOF = 0;
        public const int _string = 1;
        public const int maxT = 5;

        private const bool T = true;
        private const bool x = false;
        private const int minErrDist = 2;

        private static readonly bool[,] set =
            {
                {T, x, x, x, x, x, x}
            };

        private readonly List<Sequence> _sequences = new List<Sequence>();
        private int errDist = minErrDist;

        public Errors errors;

        public Token la; // lookahead token
        public FastaScanner scanner;
        public Token t; // last recognized token

        public FastaParser(FastaScanner scanner)
        {
            this.scanner = scanner;
            errors = new Errors();
        }

        public List<Sequence> Sequences
        {
            get { return _sequences; }
        }

        private void SynErr(int n)
        {
            if (errDist >= minErrDist)
                errors.SynErr(la.line, la.col, n);
            errDist = 0;
        }

        public void SemErr(string msg)
        {
            if (errDist >= minErrDist)
                errors.SemErr(t.line, t.col, msg);
            errDist = 0;
        }

        private void Get()
        {
            for (;;)
            {
                t = la;
                la = scanner.Scan();

                if (la.kind <= maxT)
                {
                    ++errDist;
                    break;
                }

                la = t;
            }
        }

        private void Expect(int n)
        {
            if (la.kind == n)
                Get();
            else
            {
                SynErr(n);
            }
        }

        private bool StartOf(int s)
        {
            return set[s, la.kind];
        }

        private void ExpectWeak(int n, int follow)
        {
            if (la.kind == n)
                Get();
            else
            {
                SynErr(n);

                while (!StartOf(follow))
                    Get();
            }
        }

        private bool WeakSeparator(int n, int syFol, int repFol)
        {
            int kind = la.kind;

            if (kind == n)
            {
                Get();
                return true;
            }
            else if (StartOf(repFol))
            {
                return false;
            }
            else
            {
                SynErr(n);

                while (!(set[syFol, kind] || set[repFol, kind] || set[0, kind]))
                {
                    Get();
                    kind = la.kind;
                }
                return StartOf(syFol);
            }
        }

        private void FastaFile()
        {
            Sequence sequence = null;

            while (la.kind == 2)
            {
                FastaItem(ref sequence);
                Sequences.Add(sequence);
            }
        }

        private void FastaItem(ref Sequence sequence)
        {
            sequence = new Sequence();
            Expect(2);
            Expect(1);
            sequence.Label = t.val;

            while (la.kind == 3 || la.kind == 4)
            {
                if (la.kind == 3)
                {
                    Get();
                }
                else
                {
                    Get();
                }
            }
            Expect(1);
            sequence.Residues = t.val;

            while (la.kind == 3 || la.kind == 4)
            {
                if (la.kind == 3)
                {
                    Get();
                }
                else
                {
                    Get();
                }
            }
        }

        public void Parse()
        {
            la = new Token();
            la.val = "";
            Get();
            FastaFile();

            Expect(0);
        }
    }

    // end Parser

    public class Errors
    {
        public int count; // number of errors detected
        public string errMsgFormat = "-- line {0} col {1}: {2}"; // 0=line, 1=column, 2=text
        public TextWriter errorStream = Console.Out; // error messages go to this stream

        public void SynErr(int line, int col, int n)
        {
            string s;

            switch (n)
            {
                case 0:
                    s = "EOF expected";
                    break;

                case 1:
                    s = "string expected";
                    break;

                case 2:
                    s = "\">\" expected";
                    break;

                case 3:
                    s = "\"\\r\" expected";
                    break;

                case 4:
                    s = "\"\\n\" expected";
                    break;

                case 5:
                    s = "??? expected";
                    break;

                default:
                    s = "error " + n;
                    break;
            }
            errorStream.WriteLine(errMsgFormat, line, col, s);
            count++;
        }

        public void SemErr(int line, int col, string s)
        {
            errorStream.WriteLine(errMsgFormat, line, col, s);
            count++;
        }

        public void SemErr(string s)
        {
            errorStream.WriteLine(s);
            count++;
        }

        public void Warning(int line, int col, string s)
        {
            errorStream.WriteLine(errMsgFormat, line, col, s);
        }

        public void Warning(string s)
        {
            errorStream.WriteLine(s);
        }
    }

    // Errors

    public class FatalError : Exception
    {
        public FatalError(string m) : base(m)
        {
        }
    }
}